package com.samsung.sds.ssa.jsca.element;

import java.util.HashMap;
import java.util.Map;

public class JSScope {
	private String scopeName;
	private JSScopeType scopeType;
	private JSScope outerScope = null;
	private JSElement returnType;
	private Map<String, JSScope> innerScopes = new HashMap<String, JSScope>();
	private Map<String, JSFunctionElement> functions = new HashMap<String, JSFunctionElement>();
	private Map<String, JSClassElement> classes = new HashMap<String, JSClassElement>();
	private Map<String, JSElement> variables = new HashMap<String, JSElement>();
	private Map<String, JSElement> instances = new HashMap<String, JSElement>();
	private Map<String, JSFunctionElement> instanceMethods = new HashMap<String, JSFunctionElement>();
	private Map<String, JSFunctionElement> statements = new HashMap<String, JSFunctionElement>();
	private String filePath;

	/*
	 * Constructors
	 */

	public JSScope() {
		super();
	}

	public JSScope(String scopeName) {
		super();
		this.scopeName = scopeName;
	}

	public JSScope(JSScope outerScope) {
		super();
		this.outerScope = outerScope;
	}

	public JSScope(String scopeName, JSScope outerScope) {
		super();
		this.scopeName = scopeName;
		this.outerScope = outerScope;
	}

	public JSScope(String scopeName, String filePath) {
		super();
		this.scopeName = scopeName;
		this.filePath = filePath;
	}

	/*
	 * getters and setters
	 */

	public JSScope getOuterScope() {
		return outerScope;
	}

	public void setOuterScope(JSScope outerScope) {
		this.outerScope = outerScope;
	}

	public Map<String, JSScope> getInnerScopes() {
		return innerScopes;
	}

	public void setInnerScopes(Map<String, JSScope> innerScopes) {
		this.innerScopes = innerScopes;
	}

	public JSScope getInnerScope(String scopeName) {
		return innerScopes.get(scopeName);
	}

	public void setInnerScope(String scopeName, JSScope innerScope) {
		innerScopes.put(scopeName, innerScope);
	}

	public Map<String, JSFunctionElement> getFunctions() {
		return functions;
	}

	public void setFunctions(Map<String, JSFunctionElement> functions) {
		this.functions = functions;
	}

	public Map<String, JSClassElement> getClasses() {
		return classes;
	}

	public void setClasses(Map<String, JSClassElement> classes) {
		this.classes = classes;
	}

	public Map<String, JSElement> getVariables() {
		return variables;
	}

	public void setVariables(Map<String, JSElement> variables) {
		this.variables = variables;
	}

	public Map<String, JSElement> getInstances() {
		return instances;
	}

	public void setInstances(Map<String, JSElement> instances) {
		this.instances = instances;
	}

	public Map<String, JSFunctionElement> getInstanceMethods() {
		return instanceMethods;
	}

	public void setInstanceMethods(Map<String, JSFunctionElement> instanceMethods) {
		this.instanceMethods = instanceMethods;
	}

	public String getScopeName() {
		return scopeName;
	}

	public void setScopeName(String scopeName) {
		this.scopeName = scopeName;
	}

	public JSScopeType getScopeType() {
		return scopeType;
	}

	public void setScopeType(JSScopeType scopeType) {
		this.scopeType = scopeType;
	}

	public Map<String, JSFunctionElement> getStatements() {
		return statements;
	}

	public void setStatements(Map<String, JSFunctionElement> statements) {
		this.statements = statements;
	}

	public String getFilePath() {
		return filePath;
	}

	public void setFilePath(String filePath) {
		this.filePath = filePath;
	}

	public JSElement getReturnType() {
		return returnType;
	}

	public void setReturnType(JSElement returnType) {
		this.returnType = returnType;
	}

	/*
	 * 
	 */

	/**
	 * 
	 * @param variableName
	 * @return
	 */
	public JSElement getVariable(String variableName) {
		if (variables.containsKey(variableName)) {
			return variables.get(variableName);
		}

		return null;
	}

	/**
	 * 
	 * @param variableName
	 * @param aVariable
	 */
	public void addVariable(String variableName, JSElement aVariable) {
		variables.put(variableName, aVariable);
		aVariable.getScope().setOuterScope(this);
		System.out.println(variableName + " variable is added on " + scopeName);
	}

	/**
	 * 
	 * @param instances
	 */
	public void addVariables(Map<String, JSElement> instances) {
		Map<String, JSElement> castedInstances = new HashMap<String, JSElement>();

		for (String key : instances.keySet()) {
			JSElement instance = instances.get(key);
			JSElement variable;

			if (instance instanceof JSInstanceVariableElement) {
				variable = JSElement.castInstanceToVariable((JSInstanceVariableElement) instance);
			} else {
				variable = instance;
			}

			variable.getScope().setOuterScope(this);
			castedInstances.put(variable.getName(), variable);
		}

		variables.putAll(castedInstances);
	}

	/**
	 * 
	 * @param variableName
	 */
	public void removeVariable(String variableName) {
		variables.remove(variableName);
		System.out.println(variableName + " variable is removed from " + scopeName);
	}

	/**
	 * 
	 * @param variableName
	 * @return
	 */
	public boolean hasVariable(String variableName) {
		return variables.containsKey(variableName);
	}

	/**
	 * 
	 * @param functionName
	 * @return
	 */
	public JSFunctionElement getFunction(String functionName) {
		return functions.get(functionName);
	}

	/**
	 * 
	 * @param functionName
	 * @return
	 */
	public JSScope getFunctionScope(String functionName) {
		JSFunctionElement function = getFunction(functionName);

		if (function != null) {
			return function.getScope();
		}

		return null;
	}

	/**
	 * 
	 * @param functionName
	 * @param functionElement
	 */
	public void addFunction(String functionName, JSFunctionElement functionElement) {
		functions.put(functionName, functionElement);
		functionElement.getScope().setOuterScope(this);
		System.out.println(functionName + " function is added on " + scopeName);
	}

	/**
	 * 
	 * @param instanceMethods
	 */
	public void addFunctions(Map<String, JSFunctionElement> instanceMethods) {
		Map<String, JSFunctionElement> castedInstanceMethods = new HashMap<String, JSFunctionElement>();

		for (String key : instanceMethods.keySet()) {
			JSFunctionElement instanceMethod = instanceMethods.get(key);
			JSFunctionElement function;

			if (instanceMethod instanceof JSInstanceMethodElement) {
				function = (JSFunctionElement) instanceMethod;
			} else {
				function = instanceMethod;
			}

			function.getScope().setOuterScope(this);
			castedInstanceMethods.put(function.getName(), function);
		}

		this.functions.putAll(castedInstanceMethods);
	}

	/**
	 * 
	 * @param functionName
	 */
	public void removeFunction(String functionName) {
		functions.remove(functionName);
		System.out.println(functionName + " function is removed from " + scopeName);
	}

	/**
	 * 
	 * @param functionName
	 * @return
	 */
	public boolean hasFunction(String functionName) {
		return functions.containsKey(functionName);
	}

	/**
	 * 
	 * @param className
	 * @return
	 */
	public JSClassElement getClassElement(String className) {
		return classes.get(className);
	}

	/**
	 * 
	 * @param className
	 * @return
	 */
	public JSScope getClassScope(String className) {
		JSClassElement classElement = getClassElement(className);

		if (classElement != null) {
			return classElement.getScope();
		}

		return null;
	}

	/**
	 * 
	 * @param className
	 * @param classElement
	 */
	public void addClass(String className, JSClassElement classElement) {
		classes.put(className, classElement);
		classElement.getScope().setOuterScope(this);
		System.out.println(className + " class is added on " + scopeName);
	}

	/**
	 * 
	 * @param className
	 */
	public void removeClass(String className) {
		classes.remove(className);
		System.out.println(className + " class is removed from " + scopeName);
	}

	/**
	 * 
	 * @param className
	 * @return
	 */
	public boolean hasClass(String className) {
		return classes.containsKey(className);
	}

	/**
	 * 
	 * @param instanceName
	 * @return
	 */
	public JSElement getInstanceVariable(String instanceName) {
		return instances.get(instanceName);
	}

	/**
	 * 
	 * @param instanceName
	 * @return
	 */
	public JSScope getInstanceVariableScope(String instanceName) {
		JSElement instance = getInstanceVariable(instanceName);

		if (instance != null) {
			return instance.getScope();
		}

		return null;
	}

	/**
	 * 
	 * @param instanceName
	 * @param anInstance
	 */
	public void addInstanceVariable(String instanceName, JSElement anInstance) {
		instances.put(instanceName, anInstance);
		System.out.println(instanceName + " instance is added on " + scopeName);
	}

	/**
	 * 
	 * @param instanceName
	 */
	public void removeInstanceVariable(String instanceName) {
		instances.remove(instanceName);
		System.out.println(instanceName + " instance is removed from " + scopeName);
	}

	/**
	 * 
	 * @param instanceName
	 * @return
	 */
	public boolean hasInstanceVariable(String instanceName) {
		return instances.containsKey(instanceName);
	}

	/**
	 * 
	 * @param instanceName
	 * @return
	 */
	public JSFunctionElement getInstanceMethod(String instanceName) {
		return instanceMethods.get(instanceName);
	}

	/**
	 * 
	 * @param instanceName
	 * @return
	 */
	public JSScope getInstanceMethodScope(String instanceName) {
		JSFunctionElement instanceMethod = getInstanceMethod(instanceName);

		if (instanceMethod != null) {
			return instanceMethod.getScope();
		}

		return null;
	}

	/**
	 * 
	 * @param instanceName
	 * @param instanceElement
	 */
	public void addInstanceMethod(String instanceName, JSFunctionElement instanceElement) {
		instanceMethods.put(instanceName, instanceElement);
		instanceElement.getScope().setOuterScope(this);
		System.out.println(instanceName + " instance method is added on " + scopeName);
	}

	/**
	 * 
	 * @param instanceName
	 */
	public void removeInstanceMethod(String instanceName) {
		instanceMethods.remove(instanceName);
		System.out.println(instanceName + " instance method is removed from " + scopeName);
	}

	/**
	 * 
	 * @param instanceName
	 * @return
	 */
	public boolean hasInstanceMethod(String instanceName) {
		return instanceMethods.containsKey(instanceName);
	}

	/**
	 * 
	 * @param statementName
	 * @return
	 */
	public JSFunctionElement getStatement(String statementName) {
		return statements.get(statementName);
	}

	/**
	 * 
	 * @param statementName
	 * @return
	 */
	public JSScope getStatementScope(String statementName) {
		JSFunctionElement statement = getStatement(statementName);

		if (statement != null) {
			return statement.getScope();
		}

		return null;
	}

	/**
	 * 
	 * @param statementName
	 * @param statement
	 */
	public void addStatement(String statementName, JSFunctionElement statement) {
		statements.put(statementName, statement);
		statement.getScope().setOuterScope(this);
		System.out.println(statementName + " statement is added on " + scopeName);
	}

	/**
	 * 
	 * @param statementName
	 */
	public void removeStatement(String statementName) {
		statements.remove(statementName);
		System.out.println(statementName + " statement is removed from " + scopeName);
	}

	/**
	 * 
	 * @param statementName
	 * @return
	 */
	public boolean hasStatement(String statementName) {
		return statements.containsKey(statementName);
	}

	/**
	 * 
	 * @param instanceName
	 * @return
	 */
	public boolean hasMemberDeclaration(String instanceName) {
		if (hasInstanceVariable(instanceName)) {
			return true;
		}

		if (hasClass(instanceName)) {
			return true;
		}

		if (hasInstanceMethod(instanceName)) {
			return true;
		}

		return false;
	}

	/**
	 * 
	 * @param instanceName
	 * @return
	 */
	public JSScope getMemberScope(String instanceName) {
		if (hasInstanceVariable(instanceName)) {
			return getInstanceVariableScope(instanceName);
		}

		if (hasClass(instanceName)) {
			return getClassScope(instanceName);
		}

		if (hasInstanceMethod(instanceName)) {
			return getInstanceMethodScope(instanceName);
		}

		return null;
	}

}
